/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
#include "qbsp.h"
#include "../common/aselib.h"
#include <assert.h>

#define SURF_WIDTH	2048
#define SURF_HEIGHT 2048

#define GROW_VERTS		512
#define GROW_INDICES	512
#define GROW_SURFACES	128

#define VectorSet(v, x, y, z)		v[0] = x;v[1] = y;v[2] = z;

void QuakeTextureVecs( 	plane_t *plane, vec_t shift[2], vec_t rotate, vec_t scale[2], vec_t mappingVecs[2][4] );

typedef struct {
	shaderInfo_t	*shader;
	int				x, y;

	int				maxVerts;
	int				numVerts;
	drawVert_t		*verts;

	int				maxIndexes;
	int				numIndexes;
	int				*indexes;
} terrainSurf_t;

static terrainSurf_t	*surfaces = NULL;
static terrainSurf_t	*lastSurface = NULL;
static int				numsurfaces = 0;
static int				maxsurfaces = 0;

/*
================
ShaderForLayer
================
*/
shaderInfo_t *ShaderForLayer( int minlayer, int maxlayer, const char *shadername ) {
	char	shader[ 256 ];

	if ( minlayer == maxlayer ) {
		sprintf( shader, "textures/%s_%d", shadername, maxlayer );
	} else {
		sprintf( shader, "textures/%s_%dto%d", shadername, minlayer, maxlayer );
	}

	return ShaderInfoForShader( shader );
}

/*
================
CompareVert
================
*/
qboolean CompareVert( drawVert_t *v1, drawVert_t *v2, qboolean checkst ) {
	int i;

	for( i = 0; i < 3; i++ ) {
		if ( floor( v1->xyz[ i ] + 0.1f ) != floor( v2->xyz[ i ] + 0.1f ) ) {
			return qfalse;
		}
		if ( checkst && ( ( v1->st[ 0 ] != v2->st[ 0 ] ) || ( v1->st[ 1 ] != v2->st[ 1 ] ) ) ) {
			return qfalse;
		}
	}

	return qtrue;
}

/*
================
LoadAlphaMap
================
*/
byte *LoadAlphaMap( int *num_layers, int *alphawidth, int *alphaheight ) {
	int			*alphamap32;
	byte		*alphamap;
	const char	*alphamapname;
	char		ext[ 128 ];
	int			width;
	int			height;
	int			layers;
	int			size;
	int			i;

	assert( alphawidth );
	assert( alphaheight );
	assert( num_layers );

	layers = atoi( ValueForKey( mapent, "layers" ) );
	if ( layers < 1 ) {
		Error ("SetTerrainTextures: invalid value for 'layers' (%d)", layers );
	}

	alphamapname = ValueForKey( mapent, "alphamap" );
	if ( !alphamapname[ 0 ] ) {
		Error ("LoadAlphaMap: No alphamap specified on terrain" );
	}

	ExtractFileExtension( alphamapname, ext);
	if ( !Q_stricmp( ext, "tga" ) ) {
		Load32BitImage( ExpandGamePath( alphamapname ), &alphamap32, &width, &height );

		size = width * height;
		alphamap = malloc( size );
		for( i = 0; i < size; i++ ) {
			alphamap[ i ] = ( ( alphamap32[ i ] & 0xff ) * layers ) / 256;
			if ( alphamap[ i ] >= layers ) {
				alphamap[ i ] = layers - 1;
			}
		}
	} else {
		Load256Image( ExpandGamePath( alphamapname ), &alphamap, NULL, &width, &height );
		size = width * height;
		for( i = 0; i < size; i++ ) {
			if ( alphamap[ i ] >= layers ) {
				alphamap[ i ] = layers - 1;
			}
		}
	}

	if ( ( width < 2 ) || ( height < 2 ) ) {
		Error ("LoadAlphaMap: alphamap width/height must be at least 2x2." );
	}

	*num_layers		= layers;
	*alphawidth		= width;
	*alphaheight	= height;

	return alphamap;
}

/*
================
CalcTerrainSize
================
*/
void CalcTerrainSize( vec3_t mins, vec3_t maxs, vec3_t size ) {
	bspbrush_t	*brush;
	int			i;
	const char  *key;

	// calculate the size of the terrain
	ClearBounds( mins, maxs );
	for( brush = mapent->brushes; brush != NULL; brush = brush->next ) {
		AddPointToBounds( brush->mins, mins, maxs );
		AddPointToBounds( brush->maxs, mins, maxs );
	}

	key = ValueForKey( mapent, "min" ); 
	if ( key[ 0 ] ) {
		GetVectorForKey( mapent, "min", mins );
	}

	key = ValueForKey( mapent, "max" ); 
	if ( key[ 0 ] ) {
		GetVectorForKey( mapent, "max", maxs );
	}

	for( i = 0; i < 3; i++ ) {
		mins[ i ] =  floor( mins[ i ] + 0.1 );
		maxs[ i ] =  floor( maxs[ i ] + 0.1 );
	}

	VectorSubtract( maxs, mins, size );

	if ( ( size[ 0 ] <= 0 ) || ( size[ 1 ] <= 0 ) ) {
		Error ("CalcTerrainSize: Invalid terrain size: %fx%f", size[ 0 ], size[ 1 ] );
	}
}

/*
==================
IsTriangleDegenerate

Returns qtrue if all three points are collinear or backwards
===================
*/
#define	COLINEAR_AREA	10
static qboolean	IsTriangleDegenerate( drawVert_t *points, int a, int b, int c ) {
	vec3_t		v1, v2, v3;
	float		d;

	VectorSubtract( points[b].xyz, points[a].xyz, v1 );
	VectorSubtract( points[c].xyz, points[a].xyz, v2 );
	CrossProduct( v1, v2, v3 );
	d = VectorLength( v3 );

	// assume all very small or backwards triangles will cause problems
	if ( d < COLINEAR_AREA ) {
		return qtrue;
	}

	return qfalse;
}

/*
===============
SideAsTriFan

The surface can't be represented as a single tristrip without
leaving a degenerate triangle (and therefore a crack), so add
a point in the middle and create (points-1) triangles in fan order
===============
*/
static void SideAsTriFan( terrainSurf_t *surf, int *index, int num ) {
	int					i;
	int					colorSum[4];
	drawVert_t			*mid, *v;

	// make sure we have enough space for a new vert
	if ( surf->numVerts >= surf->maxVerts ) {
		surf->maxVerts += GROW_VERTS;
		surf->verts = realloc( surf->verts, surf->maxVerts * sizeof( *surf->verts ) );
	}

	// create a new point in the center of the face
	mid = &surf->verts[ surf->numVerts ];
	surf->numVerts++;

	VectorClear(mid->xyz);
	mid->st[0] = mid->st[1] = 0;

	colorSum[0] = colorSum[1] = colorSum[2] = colorSum[3] = 0;

	for (i = 0 ; i < num; i++ ) {
		v = &surf->verts[ index[ i ] ];
		VectorAdd( mid->xyz, v->xyz, mid->xyz );
		mid->st[0] += v->st[0];
		mid->st[1] += v->st[1];

		colorSum[0] += v->color[0];
		colorSum[1] += v->color[1];
		colorSum[2] += v->color[2];
		colorSum[3] += v->color[3];
	}

	mid->xyz[0] /= num;
	mid->xyz[1] /= num;
	mid->xyz[2] /= num;

	mid->st[0] /= num;
	mid->st[1] /= num;

	mid->color[0] = colorSum[0] / num;
	mid->color[1] = colorSum[1] / num;
	mid->color[2] = colorSum[2] / num;
	mid->color[3] = colorSum[3] / num;

	// fill in indices in trifan order
	if ( surf->numIndexes + num * 3 > surf->maxIndexes ) {
		surf->maxIndexes = surf->numIndexes + num * 3;
		surf->indexes = realloc( surf->indexes, surf->maxIndexes * sizeof( *surf->indexes ) );
	}


	for ( i = 0 ; i < num; i++ ) {
		surf->indexes[ surf->numIndexes++ ] = surf->numVerts - 1;
		surf->indexes[ surf->numIndexes++ ] = index[ i ];
		surf->indexes[ surf->numIndexes++ ] = index[ (i+1) % ( surf->numVerts - 1 ) ];
	}
}
/*
================
SideAsTristrip

Try to create indices that make (points-2) triangles in tristrip order
================
*/
static void SideAsTristrip( terrainSurf_t *surf, int *index, int num ) {
	int					i;
	int					rotate;
	int					numIndices;
	int					ni;
	int					a, b, c;
	int					indices[ SHADER_MAX_INDEXES ];

	// determine the triangle strip order
	numIndices = ( num - 2 ) * 3;
	if ( numIndices > SHADER_MAX_INDEXES ) {
		Error( "SHADER_MAX_INDEXES exceeded for surface" );
	}

	// try all possible orderings of the points looking
	// for a strip order that isn't degenerate
	for ( rotate = 0 ; rotate < num; rotate++ ) {
		for ( ni = 0, i = 0 ; i < num - 2 - i ; i++ ) {
			a = index[ ( num - 1 - i + rotate ) % num ];
			b = index[ ( i + rotate ) % num ];
			c = index[ ( num - 2 - i + rotate ) % num ];

			if ( IsTriangleDegenerate( surf->verts, a, b, c ) ) {
				break;
			}
			indices[ni++] = a;
			indices[ni++] = b;
			indices[ni++] = c;

			if ( i + 1 != num - 1 - i ) {
				a = index[ ( num - 2 - i + rotate ) % num ];
				b = index[ ( i + rotate ) % num ];
				c = index[ ( i + 1 + rotate ) % num ];

				if ( IsTriangleDegenerate( surf->verts, a, b, c ) ) {
					break;
				}
				indices[ni++] = a;
				indices[ni++] = b;
				indices[ni++] = c;
			}
		}
		if ( ni == numIndices ) {
			break;		// got it done without degenerate triangles
		}
	}

	// if any triangle in the strip is degenerate,
	// render from a centered fan point instead
	if ( ni < numIndices ) {
		SideAsTriFan( surf, index, num );
		return;
	}

	// a normal tristrip
	if ( surf->numIndexes + ni > surf->maxIndexes ) {
		surf->maxIndexes = surf->numIndexes + ni;
		surf->indexes = realloc( surf->indexes, surf->maxIndexes * sizeof( *surf->indexes ) );
	}

	memcpy( surf->indexes + surf->numIndexes, indices, ni * sizeof( *surf->indexes ) );
	surf->numIndexes += ni;
}

/*
================
CreateTerrainSurface
================
*/
void CreateTerrainSurface( terrainSurf_t *surf, shaderInfo_t *shader ) {
	int					i, j, k;
	drawVert_t			*out;
	drawVert_t			*in;
	mapDrawSurface_t	*newsurf;
	float				d;

	newsurf = AllocDrawSurf();

	newsurf->terrain		= qtrue;
	newsurf->miscModel		= qtrue;
	newsurf->clipModel		= qfalse;
	newsurf->shaderInfo		= shader;
	newsurf->numIndexes		= surf->numIndexes;
	newsurf->numVerts		= surf->numVerts;

	// copy the indices
	newsurf->indexes = malloc( surf->numIndexes * sizeof( *newsurf->indexes ) );
	memcpy( newsurf->indexes, surf->indexes, surf->numIndexes * sizeof( *newsurf->indexes ) );

	// allocate the vertices
	newsurf->verts = malloc( surf->numVerts * sizeof( *newsurf->verts ) );
	memset( newsurf->verts, 0, surf->numVerts * sizeof( *newsurf->verts ) );

	// calculate the surface verts
	out = newsurf->verts;
	for( i = 0; i < newsurf->numVerts; i++, out++ ) {
		VectorCopy( surf->verts[ i ].xyz, out->xyz );

		// set the texture coordinates
		out->st[ 0 ] = surf->verts[ i ].st[ 0 ];
		out->st[ 1 ] = surf->verts[ i ].st[ 1 ];

		// the colors will be set by the lighting pass
		out->color[0] = surf->verts[ i ].color[ 3 ];
		out->color[1] = surf->verts[ i ].color[ 3 ];
		out->color[2] = surf->verts[ i ].color[ 3 ];
		out->color[3] = surf->verts[ i ].color[ 3 ];

		// calculate the vertex normal
		VectorClear( out->normal );
		VectorClear( out->tangents[0] );
		VectorClear( out->tangents[1] );
		for( j = 0; j < numsurfaces; j++ ) {
			in = surfaces[ j ].verts;
			for( k = 0; k < surfaces[ j ].numVerts; k++, in++ ) {
				if ( CompareVert( out, in, qfalse ) ) {
					VectorAdd( out->normal, in->normal, out->normal );
					VectorAdd( out->tangents[0], in->tangents[0], out->tangents[0] );
					VectorAdd( out->tangents[1], in->tangents[1], out->tangents[1] );
				}
			}
		}

		VectorNormalize( out->normal, out->normal );

		d = DotProduct(out->tangents[0], out->normal);
		VectorMA(out->tangents[0], -d, out->normal, out->tangents[0]);
		VectorNormalize(out->tangents[0], out->tangents[0]);

		d = DotProduct(out->tangents[1], out->normal);
		VectorMA(out->tangents[1], -d, out->normal, out->tangents[1]);
		VectorNormalize(out->tangents[1], out->tangents[1]);
	}
}

/*
================
EmitTerrainVerts
================
*/
void EmitTerrainVerts( side_t *side, terrainSurf_t *surf, int minlayer, int maxlayer, int alpha[ MAX_POINTS_ON_WINDING ], qboolean projecttexture ) {
	int			i;
	int			j;
	drawVert_t	*vert;
	int			*indices;
	int			numindices;
	int			maxindices;
	int			xyplane;
	vec3_t		xynorm = { 0, 0, 1 };
	vec_t		shift[ 2 ] = { 0, 0 };
	vec_t		scale[ 2 ] = { 0.5, 0.5 };
	float		vecs[ 2 ][ 4 ];
	vec3_t		xyz[3];
	vec2_t		st[3];
	float		edge[2][5];
	vec3_t		normal, cross;
	vec3_t		tangents[2];
	static int numtimes = 0;

	numtimes++;

	if ( !surf->verts ) {
		surf->numVerts		= 0;
		surf->maxVerts		= GROW_VERTS;
		surf->verts			= malloc( surf->maxVerts * sizeof( *surf->verts ) );

		surf->numIndexes	= 0;
		surf->maxIndexes	= GROW_INDICES;
		surf->indexes		= malloc( surf->maxIndexes * sizeof( *surf->indexes ) );
	}

	// calculate the texture coordinate vectors
	xyplane = FindFloatPlane( xynorm, 0 );
	QuakeTextureVecs( &mapplanes[ xyplane ], shift, 0, scale, vecs );

	// Compute tangents
	for (i = 0; i < 3; i++){
		xyz[i][ 0 ] = floor( side->winding->p[ i ][ 0 ] + 0.1f );
		xyz[i][ 1 ] = floor( side->winding->p[ i ][ 1 ] + 0.1f );
		xyz[i][ 2 ] = floor( side->winding->p[ i ][ 2 ] + 0.1f );

		// set the texture coordinates
		if ( projecttexture ) {
			st[i][0] = ( vecs[0][3] + DotProduct( vecs[ 0 ], xyz[i] ) ) / surf->shader->width;
			st[i][1] = ( vecs[1][3] + DotProduct( vecs[ 1 ], xyz[i] ) ) / surf->shader->height;
		} else {
			st[i][0] = ( side->vecs[0][3] + DotProduct( side->vecs[ 0 ], xyz[i] ) ) / surf->shader->width;
			st[i][1] = ( side->vecs[1][3] + DotProduct( side->vecs[ 1 ], xyz[i] ) ) / surf->shader->height;
		}
	}

	// Find edges
	edge[0][0] = xyz[1][0] - xyz[0][0];
	edge[0][1] = xyz[1][1] - xyz[0][1];
	edge[0][2] = xyz[1][2] - xyz[0][2];
	edge[0][3] = st[1][0] - st[0][0];
	edge[0][4] = st[1][1] - st[0][1];

	edge[1][0] = xyz[2][0] - xyz[0][0];
	edge[1][1] = xyz[2][1] - xyz[0][1];
	edge[1][2] = xyz[2][2] - xyz[0][2];
	edge[1][3] = st[2][0] - st[0][0];
	edge[1][4] = st[2][1] - st[0][1];

	// Compute normal vector
	VectorCopy(mapplanes[side->planenum].normal, normal);

	// Compute first tangent vector
	tangents[0][0] = edge[1][4] * edge[0][0] - edge[1][0] * edge[0][4];
	tangents[0][1] = edge[1][4] * edge[0][1] - edge[1][1] * edge[0][4];
	tangents[0][2] = edge[1][4] * edge[0][2] - edge[1][2] * edge[0][4];

	VectorNormalize(tangents[0], tangents[0]);

	// Compute second tangent vector
	tangents[1][0] = edge[1][0] * edge[0][3] - edge[1][3] * edge[0][0];
	tangents[1][1] = edge[1][1] * edge[0][3] - edge[1][3] * edge[0][1];
	tangents[1][2] = edge[1][2] * edge[0][3] - edge[1][3] * edge[0][2];

	VectorNormalize(tangents[1], tangents[1]);

	// Inverse tangent vectors if needed
	CrossProduct(tangents[1], tangents[0], cross);
	if (DotProduct(cross, normal) < 0.0f){
		VectorInverse(tangents[0]);
		VectorInverse(tangents[1]);
	}

	// emit the vertexes
	numindices = 0;
	maxindices = surf->maxIndexes;
	indices = malloc ( maxindices * sizeof( *indices ) );

	for ( i = 0; i < side->winding->numpoints; i++ ) {
		vert = &surf->verts[ surf->numVerts ];

		// set the final alpha value--0 for texture 1, 255 for texture 2
//		if ( alpha[ i ] < maxlayer ) {
//			vert->color[3] = 0;
//		} else {
//			vert->color[3] = 255;
//		}
		// For OverDose to work properly we need to invert these.
		// We could simply use inverseVertexColor for the first stage and
		// vertexColor for the second stage in the material, but to keep
		// things similar to DooM 3, we just invert the values here.
		if (minlayer == maxlayer)
			vert->color[3] = 255;
		else {
			if (alpha[i] < maxlayer)
				vert->color[3] = 255;
			else
				vert->color[3] = 0;
		}

		vert->xyz[ 0 ] = floor( side->winding->p[ i ][ 0 ] + 0.1f );
		vert->xyz[ 1 ] = floor( side->winding->p[ i ][ 1 ] + 0.1f );
		vert->xyz[ 2 ] = floor( side->winding->p[ i ][ 2 ] + 0.1f );

		// set the texture coordinates
		if ( projecttexture ) {
			vert->st[0] = ( vecs[0][3] + DotProduct( vecs[ 0 ], vert->xyz ) ) / surf->shader->width;
			vert->st[1] = ( vecs[1][3] + DotProduct( vecs[ 1 ], vert->xyz ) ) / surf->shader->height;
		} else {
			vert->st[0] = ( side->vecs[0][3] + DotProduct( side->vecs[ 0 ], vert->xyz ) ) / surf->shader->width;
			vert->st[1] = ( side->vecs[1][3] + DotProduct( side->vecs[ 1 ], vert->xyz ) ) / surf->shader->height;
		}

		VectorCopy( mapplanes[ side->planenum ].normal, vert->normal );

		VectorCopy(tangents[0], vert->tangents[0]);
		VectorCopy(tangents[1], vert->tangents[1]);

		for( j = 0; j < surf->numVerts; j++ ) {
			if ( CompareVert( vert, &surf->verts[ j ], qtrue ) ) {
				break;
			}
		}
		
		if ( numindices >= maxindices ) {
			maxindices += GROW_INDICES;
			indices = realloc( indices, maxindices * sizeof( *indices ) );
		}

		if ( j != surf->numVerts ) {
			indices[ numindices++ ] = j;
		} else {
			indices[ numindices++ ] = surf->numVerts;
			surf->numVerts++;
			if ( surf->numVerts >= surf->maxVerts ) {
				surf->maxVerts += GROW_VERTS;
				surf->verts = realloc( surf->verts, surf->maxVerts * sizeof( *surf->verts ) );
			}
		}
	}

	SideAsTristrip( surf, indices, numindices );

	free( indices );
}

/*
================
SurfaceForShader
================
*/
terrainSurf_t *SurfaceForShader( shaderInfo_t *shader, int x, int y ) {
	int i;

	if ( lastSurface && ( lastSurface->shader == shader ) && ( lastSurface->x == x ) && ( lastSurface->y == y ) ) {
		return lastSurface;
	}

	lastSurface = surfaces;
	for( i = 0; i < numsurfaces; i++, lastSurface++ ) {
		if ( ( lastSurface->shader == shader ) && ( lastSurface->x == x ) && ( lastSurface->y == y ) ) {
			return lastSurface;
		}
	}

	if ( numsurfaces >= maxsurfaces ) {
		maxsurfaces += GROW_SURFACES;
		surfaces = realloc( surfaces, maxsurfaces * sizeof( *surfaces ) );
		memset( surfaces + numsurfaces + 1, 0, ( maxsurfaces - numsurfaces - 1 ) * sizeof( *surfaces ) );
	}

	lastSurface= &surfaces[ numsurfaces++ ];
	lastSurface->shader = shader;
	lastSurface->x = x;
	lastSurface->y = y;

	return lastSurface;
}

/*
================
SetTerrainTextures
================
*/
void SetTerrainTextures( void ) {
	int				i;
	int				x, y;
	int				layer;
	int				minlayer, maxlayer;
	float			s, t;
	float			min_s, min_t;
	int				alpha[ MAX_POINTS_ON_WINDING ];
	shaderInfo_t	*si;//, *terrainShader;
	bspbrush_t		*brush;
	side_t			*side;
	const char		*shadername;
	vec3_t			mins, maxs;
	vec3_t			size;
	int				surfwidth, surfheight, surfsize;
	terrainSurf_t	*surf;
	byte			*alphamap;
	int				alphawidth, alphaheight;
	int				num_layers;
	extern qboolean	onlyents;

	if ( onlyents ) {
		return;
	}

	shadername = ValueForKey( mapent, "material" );
	if ( !shadername[ 0 ] ) {
		Error ("SetTerrainTextures: shader not specified" );
	}

	alphamap = LoadAlphaMap( &num_layers, &alphawidth, &alphaheight );

	mapent->firstDrawSurf = numMapDrawSurfs;

	// calculate the size of the terrain
	CalcTerrainSize( mins, maxs, size );

	surfwidth	= ( size[ 0 ] + SURF_WIDTH - 1 ) / SURF_WIDTH;
	surfheight	= ( size[ 1 ] + SURF_HEIGHT - 1 ) / SURF_HEIGHT;
	surfsize = surfwidth * surfheight;

	lastSurface = NULL;
	numsurfaces = 0;
	maxsurfaces = 0;
	for( i = num_layers; i > 0; i-- ) {
		maxsurfaces += i * surfsize;
	}
	surfaces = malloc( maxsurfaces * sizeof( *surfaces ) );
	memset( surfaces, 0, maxsurfaces * sizeof( *surfaces ) );

//	terrainShader = ShaderInfoForShader( "textures/common/terrain" );

	for( brush = mapent->brushes; brush != NULL; brush = brush->next ) {
		// only create surfaces for sides marked as terrain
		for( side = brush->sides; side < &brush->sides[ brush->numsides ]; side++ ) {
			if ( !side->shaderInfo ) {
				continue;
			}

			if ( ( ( side->surfaceFlags | side->shaderInfo->surfaceFlags ) & SURFACE_NODRAW ) && Q_stricmp( side->shaderInfo->shader, "textures/common/terrain" ) ) {
				continue;
			}

			minlayer = num_layers;
			maxlayer = 0;

			// project each point of the winding onto the alphamap to determine which
			// textures to blend
			min_s = 1.0;
			min_t = 1.0;
			for( i = 0; i < side->winding->numpoints; i++ ) {
				s = floor( side->winding->p[ i ][ 0 ] + 0.1f - mins[ 0 ] ) / size[ 0 ];
				t = floor( side->winding->p[ i ][ 1 ] + 0.1f - mins[ 1 ] ) / size[ 1 ];

				if ( s < 0 ) {
					s = 0;
				}
				
				if ( t < 0 ) {
					t = 0;
				}

				if ( s >= 1.0 ) {
					s = 1.0;
				}

				if ( t >= 1.0 ) {
					t = 1.0;
				}

				if ( s < min_s ) {
					min_s = s;
				}

				if ( t < min_t ) {
					min_t = t;
				}

				x = ( alphawidth - 1 ) * s;
				y = ( alphaheight - 1 ) * t;

				layer = alphamap[ x + y * alphawidth ];
				if ( layer < minlayer ) {
					minlayer = layer;
				}

				if ( layer > maxlayer ) {
					maxlayer = layer;
				}

				alpha[ i ] = layer;
			}

			x = min_s * surfwidth;
			if ( x >= surfwidth ) {
				x = surfwidth - 1;
			}

			y = min_t * surfheight;
			if ( y >= surfheight ) {
				y = surfheight - 1;
			}

			if ( !Q_stricmp( side->shaderInfo->shader, "textures/common/terrain" ) ) {
				si = ShaderForLayer( minlayer, maxlayer, shadername );
				side->shaderInfo = ShaderInfoForTerrain(si);

				if ( showseams ) {
					for( i = 0; i < side->winding->numpoints; i++ ) {
						if ( ( alpha[ i ] != minlayer ) && ( alpha[ i ] != maxlayer ) ) {
							si = ShaderInfoForShader( "textures/common/white" );
							break;
						}
					}
				}

				surf = SurfaceForShader( si, x, y );
				EmitTerrainVerts( side, surf, minlayer, maxlayer, alpha, qtrue );
			} else {
				si = side->shaderInfo;
				side->shaderInfo = ShaderInfoForTerrain(si);
				surf = SurfaceForShader( si, x, y );
				EmitTerrainVerts( side, surf, minlayer, maxlayer, alpha, qfalse );
			}
		}
	}

	// create the final surfaces
	for( surf = surfaces, i = 0; i < numsurfaces; i++, surf++ ) {
		if ( surf->numVerts ) {
			CreateTerrainSurface( surf, surf->shader );
		}
	}

	//
	// clean up any allocated memory
	//
	for( surf = surfaces, i = 0; i < numsurfaces; i++, surf++ ) {
		if ( surf->verts ) {
			free( surf->verts );
			free( surf->indexes );
		}
	}
	free( alphamap );
	free( surfaces );

	surfaces = NULL;
	lastSurface = NULL;
	numsurfaces = 0;
	maxsurfaces = 0;
}


/*
 =======================================================================

 TERRAIN MODELS

 =======================================================================
*/

#define TERRAIN_MAX_INDICES			300 * 3
#define TERRAIN_MAX_VERTICES		300 * 3

typedef struct terrainModelSurf_s {
	char			material[256];

	int				numIndices;
	int				indices[TERRAIN_MAX_INDICES];

	int				numVertices;
	drawVert_t		vertices[TERRAIN_MAX_VERTICES];

	struct terrainModelSurf_s	*next;
} terrainModelSurf_t;

static terrainModelSurf_t	*terrainModelSurfs;

static byte					*terrainBlendMap;
static int					terrainBlendMapWidth;
static int					terrainBlendMapHeight;


/*
 =================
 LoadBlendMap
 =================
*/
static void LoadBlendMap (const char *material){

	char	filename[1024];
	byte	*buffer;

	sprintf(filename, "%s%s_blend.tga", gamedir, material);
	if (!FileExists(filename)){
		sprintf(filename, "%s%s_blend.TGA", gamedir, material);
		if (!FileExists(filename))
			return;
	}

	LoadFileBlock(filename, &buffer);

	if (!buffer)
		return;

	LoadTGABuffer(buffer, &terrainBlendMap, &terrainBlendMapWidth, &terrainBlendMapHeight);

	free(buffer);
}

/*
 =================
 FreeBlendMap
 =================
*/
static void FreeBlendMap (void){

	if (!terrainBlendMap)
		return;

	free(terrainBlendMap);
	terrainBlendMap = NULL;
}

/*
 =================
 VertexColorFromBlendMap
 =================
*/
static void VertexColorFromBlendMap (float s, float t, byte color[4]){

	int		x, y;

	if (!terrainBlendMap)
		return;

	if (s < 0.0f)
		s = 0.0f;
	else if (s > 1.0f)
		s = 1.0f;

	if (t < 0.0f)
		t = 0.0f;
	else if (t > 1.0f)
		t = 1.0f;

	x = (terrainBlendMapWidth - 1) * s;
	y = (terrainBlendMapHeight - 1) * t;

	color[0] = terrainBlendMap[4*(y*terrainBlendMapWidth+x)+0];
	color[1] = terrainBlendMap[4*(y*terrainBlendMapWidth+x)+1];
	color[2] = terrainBlendMap[4*(y*terrainBlendMapWidth+x)+2];
	color[3] = terrainBlendMap[4*(y*terrainBlendMapWidth+x)+3];
}

/*
 =================
 EmitTerrainModelSurfaces
 =================
*/
static void EmitTerrainModelSurfaces (void){

	terrainModelSurf_t	*surf, *other;
	mapDrawSurface_t	*out;
	float				d;
	int					i, j;

	for (surf = terrainModelSurfs; surf; surf = surf->next){
		// Allocate a surface
		out = AllocDrawSurf();
		out->terrain = qtrue;
		out->miscModel = qtrue;
		out->clipModel = qtrue;
		out->clipModelName[0] = 0;
		out->shaderInfo = ShaderInfoForShader(surf->material);

		// Emit the indices
		out->numIndexes = surf->numIndices;
		out->indexes = malloc(out->numIndexes * sizeof(out->indexes[0]));

		for (i = 0; i < surf->numIndices; i += 3){
			out->indexes[i+0] = surf->indices[i+0];
			out->indexes[i+1] = surf->indices[i+1];
			out->indexes[i+2] = surf->indices[i+2];
		}

		// Emit the vertices
		out->numVerts = surf->numVertices;
		out->verts = malloc(out->numVerts * sizeof(out->verts[0]));

		for (i = 0; i < surf->numVertices; i++){
			out->verts[i].xyz[0] = surf->vertices[i].xyz[0];
			out->verts[i].xyz[1] = surf->vertices[i].xyz[1];
			out->verts[i].xyz[2] = surf->vertices[i].xyz[2];

			out->verts[i].st[0] = surf->vertices[i].st[0];
			out->verts[i].st[1] = surf->vertices[i].st[1];

			out->verts[i].color[0] = surf->vertices[i].color[0];
			out->verts[i].color[1] = surf->vertices[i].color[1];
			out->verts[i].color[2] = surf->vertices[i].color[2];
			out->verts[i].color[3] = surf->vertices[i].color[3];

			// Calculate smoothed normal and tangent vectors
			VectorClear(out->verts[i].normal);
			VectorClear(out->verts[i].tangents[0]);
			VectorClear(out->verts[i].tangents[1]);

			for (other = terrainModelSurfs; other; other = other->next){
				for (j = 0; j < other->numVertices; j++){
					if (other->vertices[j].xyz[0] != out->verts[i].xyz[0] || other->vertices[j].xyz[1] != out->verts[i].xyz[1] || other->vertices[j].xyz[2] != out->verts[i].xyz[2])
						continue;

					VectorAdd(out->verts[i].normal, other->vertices[j].normal, out->verts[i].normal);
					VectorAdd(out->verts[i].tangents[0], other->vertices[j].tangents[0], out->verts[i].tangents[0]);
					VectorAdd(out->verts[i].tangents[1], other->vertices[j].tangents[1], out->verts[i].tangents[1]);
				}
			}

			VectorNormalize(out->verts[i].normal, out->verts[i].normal);

			d = DotProduct(out->verts[i].tangents[0], out->verts[i].normal);
			VectorMA(out->verts[i].tangents[0], -d, out->verts[i].normal, out->verts[i].tangents[0]);
			VectorNormalize(out->verts[i].tangents[0], out->verts[i].tangents[0]);

			d = DotProduct(out->verts[i].tangents[1], out->verts[i].normal);
			VectorMA(out->verts[i].tangents[1], -d, out->verts[i].normal, out->verts[i].tangents[1]);
			VectorNormalize(out->verts[i].tangents[1], out->verts[i].tangents[1]);
		}
	}

	// Free memory
	for (surf = terrainModelSurfs; surf; surf = other){
		other = surf->next;

		free(surf);
	}

	terrainModelSurfs = NULL;
}

/*
 =================
 AddTerrainModelSurface
 =================
*/
static void AddTerrainModelSurface (vec3_t origin, vec3_t axis[3], const char *material, int numIndices, int *indexList, int numVertices, drawVert_t *vertexList){

	terrainModelSurf_t	*surf;
	int					i;

	surf = malloc(sizeof(terrainModelSurf_t));

	surf->next = terrainModelSurfs;
	terrainModelSurfs = surf;

	// Copy the material
	strcpy(surf->material, material);

	// Copy the indices
	surf->numIndices = numIndices;

	for (i = 0; i < numIndices; i += 3){
		surf->indices[i+0] = indexList[i+0];
		surf->indices[i+1] = indexList[i+1];
		surf->indices[i+2] = indexList[i+2];
	}

	// Copy the vertices
	surf->numVertices = numVertices;

	for (i = 0; i < numVertices; i++){
		surf->vertices[i].xyz[0] = DotProduct(vertexList[i].xyz, axis[0]) + origin[0];
		surf->vertices[i].xyz[1] = DotProduct(vertexList[i].xyz, axis[1]) + origin[1];
		surf->vertices[i].xyz[2] = DotProduct(vertexList[i].xyz, axis[2]) + origin[2];

		surf->vertices[i].normal[0] = DotProduct(vertexList[i].normal, axis[0]);
		surf->vertices[i].normal[1] = DotProduct(vertexList[i].normal, axis[1]);
		surf->vertices[i].normal[2] = DotProduct(vertexList[i].normal, axis[2]);

		surf->vertices[i].tangents[0][0] = DotProduct(vertexList[i].tangents[0], axis[0]);
		surf->vertices[i].tangents[0][1] = DotProduct(vertexList[i].tangents[0], axis[1]);
		surf->vertices[i].tangents[0][2] = DotProduct(vertexList[i].tangents[0], axis[2]);
		surf->vertices[i].tangents[1][0] = DotProduct(vertexList[i].tangents[1], axis[0]);
		surf->vertices[i].tangents[1][1] = DotProduct(vertexList[i].tangents[1], axis[1]);
		surf->vertices[i].tangents[1][2] = DotProduct(vertexList[i].tangents[1], axis[2]);

		surf->vertices[i].st[0] = vertexList[i].st[0];
		surf->vertices[i].st[1] = vertexList[i].st[1];

		surf->vertices[i].color[0] = vertexList[i].color[0];
		surf->vertices[i].color[1] = vertexList[i].color[1];
		surf->vertices[i].color[2] = vertexList[i].color[2];
		surf->vertices[i].color[3] = vertexList[i].color[3];
	}
}

/*
 =================
 ProcessTerrainASEModel
 =================
*/
static void ProcessTerrainASEModel (const char *modelName, vec3_t origin, vec3_t axis[3], float surfSize){

	int					i, j, k, l;
	int					numSurfaces;
	const char			*name;
	polyset_t			*pset;
	int					numFrames;
	char				filename[1024];
	char				material[256];

	vec3_t				mins, maxs;

	drawVert_t			*triVerts;
	float				edge[2][5];
	vec3_t				normal, cross;
	vec3_t				tangents[2];

	int					indexList[SHADER_MAX_INDEXES];
	int					numIndices;

	drawVert_t			vertexList[SHADER_MAX_VERTEXES], vertex;
	int					numVertices;

	sprintf( filename, "%s%s", gamedir, modelName );

	// load the model
	ASE_Load( filename, qfalse, qfalse );

	// each ase surface will become a new bsp surface
	numSurfaces = ASE_GetNumSurfaces();

	// expand, translate, and rotate the vertexes
	// swap all the surfaces
	for ( i = 0 ; i < numSurfaces ; i++) {
		name = ASE_GetSurfaceName( i );

		pset = ASE_GetSurfaceAnimation( i, &numFrames, -1, -1, -1 );
		if ( !name || !pset ) {
			continue;
		}

		strcpy(material, pset->materialname);
		if (strchr(material, '.'))
			*strchr(material, '.') = 0;

		LoadBlendMap(material);

		// Copy vertices
		numIndices = 0;
		numVertices = 0;

		ClearBounds(mins, maxs);

		for ( j = 0 ; j < pset->numtriangles * 3 ; j++ ) {
			int		index;
			triangle_t	*tri;

			index = j % 3;
			tri = &pset->triangles[ j / 3 ];

			if (index == 0){
				// Don't overflow
				if (numIndices + 3 > TERRAIN_MAX_INDICES || numVertices + 3 > TERRAIN_MAX_VERTICES){
					AddTerrainModelSurface(origin, axis, material, numIndices, indexList, numVertices, vertexList);

					numIndices = 0;
					numVertices = 0;
				}

				// Don't create huge surfaces
				for (k = 0; k < 3; k++)
					AddPointToBounds(tri->verts[k], mins, maxs);

				if (maxs[0] - mins[0] > surfSize || maxs[1] - mins[1] > surfSize || maxs[2] - mins[2] > surfSize){
					if (numIndices && numVertices){
						AddTerrainModelSurface(origin, axis, material, numIndices, indexList, numVertices, vertexList);

						numIndices = 0;
						numVertices = 0;

						ClearBounds(mins, maxs);

						for (k = 0; k < 3; k++)
							AddPointToBounds(tri->verts[k], mins, maxs);
					}
				}

				triVerts = &vertexList[numVertices];
			}

			vertex.st[0] = tri->texcoords[index][0];
			vertex.st[1] = tri->texcoords[index][1];

			vertex.xyz[0] = tri->verts[index][0];
			vertex.xyz[1] = tri->verts[index][1];
			vertex.xyz[2] = tri->verts[index][2];

			vertex.color[0] = (byte)(255.0f * tri->colors[index][0]);
			vertex.color[1] = (byte)(255.0f * tri->colors[index][1]);
			vertex.color[2] = (byte)(255.0f * tri->colors[index][2]);
			vertex.color[3] = 255;

			// Option to look up the vertex color from a blend map
			VertexColorFromBlendMap(vertex.st[0], vertex.st[1], vertex.color);

			// Add it to the vertex list if not added yet
			if (numIndices == SHADER_MAX_INDEXES){
				printf("SHADER_MAX_INDEXES hit\n");
				return;
			}

			for (l = 0; l < numVertices; l++){
				if (vertexList[l].xyz[0] != vertex.xyz[0] || vertexList[l].xyz[1] != vertex.xyz[1] || vertexList[l].xyz[2] != vertex.xyz[2])
					continue;
				if (vertexList[i].st[0] != vertex.st[0] || vertexList[i].st[1] != vertex.st[1])
					continue;

				break;
			}

			if (l == numVertices){
				if (numVertices == SHADER_MAX_VERTEXES){
					printf("SHADER_MAX_VERTEXES hit\n");
					return;
				}

				indexList[numIndices++] = numVertices;
				vertexList[numVertices++] = vertex;
			}
			else
				indexList[numIndices++] = l;

			// Compute tangents
			if (index == 0){
				// Find edges
				edge[0][0] = triVerts[1].xyz[0] - triVerts[0].xyz[0];
				edge[0][1] = triVerts[1].xyz[1] - triVerts[0].xyz[1];
				edge[0][2] = triVerts[1].xyz[2] - triVerts[0].xyz[2];
				edge[0][3] = triVerts[1].st[0] - triVerts[0].st[0];
				edge[0][4] = triVerts[1].st[1] - triVerts[0].st[1];

				edge[1][0] = triVerts[2].xyz[0] - triVerts[0].xyz[0];
				edge[1][1] = triVerts[2].xyz[1] - triVerts[0].xyz[1];
				edge[1][2] = triVerts[2].xyz[2] - triVerts[0].xyz[2];
				edge[1][3] = triVerts[2].st[0] - triVerts[0].st[0];
				edge[1][4] = triVerts[2].st[1] - triVerts[0].st[1];

				// Compute normal vector
				normal[0] = edge[1][1] * edge[0][2] - edge[1][2] * edge[0][1];
				normal[1] = edge[1][2] * edge[0][0] - edge[1][0] * edge[0][2];
				normal[2] = edge[1][0] * edge[0][1] - edge[1][1] * edge[0][0];

				VectorNormalize(normal, normal);

				// Compute first tangent vector
				tangents[0][0] = edge[1][4] * edge[0][0] - edge[1][0] * edge[0][4];
				tangents[0][1] = edge[1][4] * edge[0][1] - edge[1][1] * edge[0][4];
				tangents[0][2] = edge[1][4] * edge[0][2] - edge[1][2] * edge[0][4];

				VectorNormalize(tangents[0], tangents[0]);

				// Compute second tangent vector
				tangents[1][0] = edge[1][0] * edge[0][3] - edge[1][3] * edge[0][0];
				tangents[1][1] = edge[1][1] * edge[0][3] - edge[1][3] * edge[0][1];
				tangents[1][2] = edge[1][2] * edge[0][3] - edge[1][3] * edge[0][2];

				VectorNormalize(tangents[1], tangents[1]);

				// Inverse tangent vectors if needed
				CrossProduct(tangents[1], tangents[0], cross);
				if (DotProduct(cross, normal) < 0.0f){
					VectorInverse(tangents[0]);
					VectorInverse(tangents[1]);
				}

				// Copy them
				VectorCopy(normal, triVerts[0].normal);
				VectorCopy(normal, triVerts[1].normal);
				VectorCopy(normal, triVerts[2].normal);

				VectorCopy(tangents[0], triVerts[0].tangents[0]);
				VectorCopy(tangents[0], triVerts[1].tangents[0]);
				VectorCopy(tangents[0], triVerts[2].tangents[0]);
				VectorCopy(tangents[1], triVerts[0].tangents[1]);
				VectorCopy(tangents[1], triVerts[1].tangents[1]);
				VectorCopy(tangents[1], triVerts[2].tangents[1]);
			}
		}

		// Add a surface
		if (numIndices && numVertices)
			AddTerrainModelSurface(origin, axis, material, numIndices, indexList, numVertices, vertexList);

		FreeBlendMap();
	}
}

/*
 =================
 ProcessTerrainLWOModel
 =================
*/
static void ProcessTerrainLWOModel (const char *modelName, vec3_t origin, vec3_t axis[3], float surfSize){

	lwObject			*inObject;
	lwLayer				*inLayer;
	lwSurface			*inSurface;
	lwPolygon			*inPolygon;
	lwPolVert			*inPolVert;
	lwPoint				*inPoint;
	lwVMapPt			*inVMapPt;
	char				material[256];
	int					defaultSTAxis[2];
	float				defaultXYZ2STScale[2];
	float				min, max, size, st[2];
	unsigned			failId;
	int					failPos;
	int					i, j, k, l;

	vec3_t				mins, maxs, xyz;

	drawVert_t			*triVerts;
	float				edge[2][5];
	vec3_t				normal, cross;
	vec3_t				tangents[2];

	int					indexList[TERRAIN_MAX_INDICES];
	int					numIndices;

	drawVert_t			vertexList[TERRAIN_MAX_VERTICES], vertex;
	int					numVertices;

	char				filename[1024];

	sprintf( filename, "%s%s", gamedir, modelName );

	// Load the file
	inObject = lwGetObject(filename, &failId, &failPos);
	if (!inObject){
		printf("Couldn't find %s\n", modelName);
		return;
	}

	if (inObject->nlayers != 1){
		printf("LWO has invalid number of layers (%i)\n", inObject->nlayers);
		return;
	}

	inLayer = &inObject->layer[0];

	// Setup default ST map
	defaultSTAxis[0] = 0;
	defaultSTAxis[1] = 1;

	st[0] = 0.0f;
	st[1] = 0.0f;

	for (i = 0; i < 3; i++){
		min = inLayer->bbox[i+0];
		max = inLayer->bbox[i+3];

		size = max - min;

		if (size > st[0]){
			defaultSTAxis[1] = defaultSTAxis[0];
			defaultSTAxis[0] = i;

			st[1] = st[0];
			st[0] = size;
		}
		else if (size > st[1]){
			defaultSTAxis[1] = i;

			st[1] = size;
		}
	}

	defaultXYZ2STScale[0] = 4.0f / st[0];
	defaultXYZ2STScale[1] = 4.0f / st[1];

	// Run through the surfaces
	inSurface = inObject->surf;

	for (i = 0; i < inObject->nsurfs; i++){
		// Copy the material
		strcpy(material, inSurface->name);
		if (strchr(material, '.'))
			*strchr(material, '.') = 0;

		LoadBlendMap(material);

		// Copy vertices
		numIndices = 0;
		numVertices = 0;

		ClearBounds(mins, maxs);

		for (j = 0, inPolygon = inLayer->polygon.pol; j < inLayer->polygon.count; j++, inPolygon++){
			if (inPolygon->surf != inSurface)
				continue;

			if (inPolygon->type != ID_FACE){
				qprintf("WARNING: polygon type != ID_FACE, discarded...\n");
				continue;
			}

			if (inPolygon->nverts != 3){
				qprintf("WARNING: polygon has %i vertices, discarded...\n", inPolygon->nverts);
				continue;
			}

			// Don't overflow
			if (numIndices + 3 > TERRAIN_MAX_INDICES || numVertices + 3 > TERRAIN_MAX_VERTICES){
				AddTerrainModelSurface(origin, axis, material, numIndices, indexList, numVertices, vertexList);

				numIndices = 0;
				numVertices = 0;
			}

			// Don't create huge surfaces
			for (k = 0, inPolVert = inPolygon->v; k < 3; k++, inPolVert++){
				inPoint = &inLayer->point.pt[inPolVert->index];

				xyz[0] = inPoint->pos[0];
				xyz[1] = inPoint->pos[2];
				xyz[2] = inPoint->pos[1];

				AddPointToBounds(xyz, mins, maxs);
			}

			if (maxs[0] - mins[0] > surfSize || maxs[1] - mins[1] > surfSize || maxs[2] - mins[2] > surfSize){
				if (numIndices && numVertices){
					AddTerrainModelSurface(origin, axis, material, numIndices, indexList, numVertices, vertexList);

					numIndices = 0;
					numVertices = 0;

					ClearBounds(mins, maxs);

					for (k = 0, inPolVert = inPolygon->v; k < 3; k++, inPolVert++){
						inPoint = &inLayer->point.pt[inPolVert->index];

						xyz[0] = inPoint->pos[0];
						xyz[1] = inPoint->pos[2];
						xyz[2] = inPoint->pos[1];

						AddPointToBounds(xyz, mins, maxs);
					}
				}
			}

			triVerts = &vertexList[numVertices];

			for (k = 0, inPolVert = inPolygon->v; k < 3; k++, inPolVert++){
				inPoint = &inLayer->point.pt[inPolVert->index];

				vertex.xyz[0] = inPoint->pos[0];
				vertex.xyz[1] = inPoint->pos[2];
				vertex.xyz[2] = inPoint->pos[1];

				vertex.st[0] = vertex.xyz[defaultSTAxis[0]] * defaultXYZ2STScale[0];
				vertex.st[1] = vertex.xyz[defaultSTAxis[1]] * defaultXYZ2STScale[1];

				vertex.color[0] = inSurface->color.rgb[0] * inSurface->diffuse.val * 255;
				vertex.color[1] = inSurface->color.rgb[1] * inSurface->diffuse.val * 255;
				vertex.color[2] = inSurface->color.rgb[2] * inSurface->diffuse.val * 255;
				vertex.color[3] = 255;

				for (l = 0, inVMapPt = inPoint->vm; l < inPoint->nvmaps; l++, inVMapPt++){
					if (inVMapPt->vmap->type == LWID_('T', 'X', 'U', 'V')){
						vertex.st[0] = inVMapPt->vmap->val[inVMapPt->index][0];
						vertex.st[1] = 1.0f - inVMapPt->vmap->val[inVMapPt->index][1];
					}

					if (inVMapPt->vmap->type == LWID_('R', 'G', 'B', 'A')){
						vertex.color[0] = inVMapPt->vmap->val[inVMapPt->index][0] * inSurface->color.rgb[0] * inSurface->diffuse.val * 255;
						vertex.color[1] = inVMapPt->vmap->val[inVMapPt->index][1] * inSurface->color.rgb[1] * inSurface->diffuse.val * 255;
						vertex.color[2] = inVMapPt->vmap->val[inVMapPt->index][2] * inSurface->color.rgb[2] * inSurface->diffuse.val * 255;
						vertex.color[3] = inVMapPt->vmap->val[inVMapPt->index][3] * 255;
					}
				}

				for (l = 0, inVMapPt = inPolVert->vm; l < inPolVert->nvmaps; l++, inVMapPt++){
					if (inVMapPt->vmap->type == LWID_('T', 'X', 'U', 'V')){
						vertex.st[0] = inVMapPt->vmap->val[inVMapPt->index][0];
						vertex.st[1] = 1.0f - inVMapPt->vmap->val[inVMapPt->index][1];
					}

					if (inVMapPt->vmap->type == LWID_('R', 'G', 'B', 'A')){
						vertex.color[0] = inVMapPt->vmap->val[inVMapPt->index][0] * inSurface->color.rgb[0] * inSurface->diffuse.val * 255;
						vertex.color[1] = inVMapPt->vmap->val[inVMapPt->index][1] * inSurface->color.rgb[1] * inSurface->diffuse.val * 255;
						vertex.color[2] = inVMapPt->vmap->val[inVMapPt->index][2] * inSurface->color.rgb[2] * inSurface->diffuse.val * 255;
						vertex.color[3] = inVMapPt->vmap->val[inVMapPt->index][3] * 255;
					}
				}

				// Option to look up the vertex color from a blend map
				VertexColorFromBlendMap(vertex.st[0], vertex.st[1], vertex.color);

				// Add it to the vertex list if not added yet
				if (numIndices == TERRAIN_MAX_INDICES){
					printf("TERRAIN_MAX_INDICES hit\n");
					return;
				}

				for (l = 0; l < numVertices; l++){
					if (vertexList[l].xyz[0] != vertex.xyz[0] || vertexList[l].xyz[1] != vertex.xyz[1] || vertexList[l].xyz[2] != vertex.xyz[2])
						continue;
					if (vertexList[i].st[0] != vertex.st[0] || vertexList[i].st[1] != vertex.st[1])
						continue;

					break;
				}

				if (l == numVertices){
					if (numVertices == TERRAIN_MAX_VERTICES){
						printf("TERRAIN_MAX_VERTICES hit\n");
						return;
					}

					indexList[numIndices++] = numVertices;
					vertexList[numVertices++] = vertex;
				}
				else
					indexList[numIndices++] = l;
			}

			// Compute tangents

			// Find edges
			edge[0][0] = triVerts[1].xyz[0] - triVerts[0].xyz[0];
			edge[0][1] = triVerts[1].xyz[1] - triVerts[0].xyz[1];
			edge[0][2] = triVerts[1].xyz[2] - triVerts[0].xyz[2];
			edge[0][3] = triVerts[1].st[0] - triVerts[0].st[0];
			edge[0][4] = triVerts[1].st[1] - triVerts[0].st[1];

			edge[1][0] = triVerts[2].xyz[0] - triVerts[0].xyz[0];
			edge[1][1] = triVerts[2].xyz[1] - triVerts[0].xyz[1];
			edge[1][2] = triVerts[2].xyz[2] - triVerts[0].xyz[2];
			edge[1][3] = triVerts[2].st[0] - triVerts[0].st[0];
			edge[1][4] = triVerts[2].st[1] - triVerts[0].st[1];

			// Compute normal vector
			normal[0] = edge[1][1] * edge[0][2] - edge[1][2] * edge[0][1];
			normal[1] = edge[1][2] * edge[0][0] - edge[1][0] * edge[0][2];
			normal[2] = edge[1][0] * edge[0][1] - edge[1][1] * edge[0][0];

			VectorNormalize(normal, normal);

			// Compute first tangent vector
			tangents[0][0] = edge[1][4] * edge[0][0] - edge[1][0] * edge[0][4];
			tangents[0][1] = edge[1][4] * edge[0][1] - edge[1][1] * edge[0][4];
			tangents[0][2] = edge[1][4] * edge[0][2] - edge[1][2] * edge[0][4];

			VectorNormalize(tangents[0], tangents[0]);

			// Compute second tangent vector
			tangents[1][0] = edge[1][0] * edge[0][3] - edge[1][3] * edge[0][0];
			tangents[1][1] = edge[1][1] * edge[0][3] - edge[1][3] * edge[0][1];
			tangents[1][2] = edge[1][2] * edge[0][3] - edge[1][3] * edge[0][2];

			VectorNormalize(tangents[1], tangents[1]);

			// Inverse tangent vectors if needed
			CrossProduct(tangents[1], tangents[0], cross);
			if (DotProduct(cross, normal) < 0.0f){
				VectorInverse(tangents[0]);
				VectorInverse(tangents[1]);
			}

			// Copy them
			VectorCopy(normal, triVerts[0].normal);
			VectorCopy(normal, triVerts[1].normal);
			VectorCopy(normal, triVerts[2].normal);

			VectorCopy(tangents[0], triVerts[0].tangents[0]);
			VectorCopy(tangents[0], triVerts[1].tangents[0]);
			VectorCopy(tangents[0], triVerts[2].tangents[0]);
			VectorCopy(tangents[1], triVerts[0].tangents[1]);
			VectorCopy(tangents[1], triVerts[1].tangents[1]);
			VectorCopy(tangents[1], triVerts[2].tangents[1]);
		}

		// Add a surface
		if (numIndices && numVertices)
			AddTerrainModelSurface(origin, axis, material, numIndices, indexList, numVertices, vertexList);

		FreeBlendMap();

		// Skip to next surface
		inSurface = inSurface->next;
	}

	lwFreeObject(inObject);
}

/*
 =================
 ProcessTerrainModel
 =================
*/
void ProcessTerrainModel (void){

	char	name[1024];
	vec3_t	origin, angles, axis[3];
	float	surfSize;

	// Get the model name
	strcpy(name, ValueForKey(mapent, "model"));
	if (!name[0])
		Error("ProcessTerrainModel: model not specified");

	// Get origin
	GetVectorForKey(mapent, "origin", origin);

	// Get angles for rotation
	if (strcmp("", ValueForKey(mapent, "angles")))
		GetVectorForKey(mapent, "angles", angles);
	else {
		angles[0] = 0.0f;
		angles[1] = FloatForKey(mapent, "angle");
		angles[2] = 0.0f;
	}

	angles[1] = -angles[1];		// HACK!
	AnglesToAxis(angles, axis);

	// Get surface size
	surfSize = FloatForKey(mapent, "size");
	if (surfSize == 0.0f)
		surfSize = 2048.0f;
	else if (surfSize < 256.0f)
		surfSize = 256.0f;

	// Process the model file
	if (strstr(name, ".ase") || strstr(name, ".ASE"))
		ProcessTerrainASEModel(name, origin, axis, surfSize);
	else if (strstr(name, ".lwo") || strstr(name, ".LWO"))
		ProcessTerrainLWOModel(name, origin, axis, surfSize);
	else
		Error("ProcessTerrainModel: invalid model type\n");

	// Emit the terrain model surfaces
	EmitTerrainModelSurfaces();
}
